{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Breaking Hardware ECC on CW305 FPGA, part 2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This builds on CW305_ECC.ipynb; be sure to digest that one first before starting this one.\n",
    "\n",
    "In this notebook, we improve the original attack and show that the Difference of Means (DoM) approach can work better than originally thought, with some small tweaks.\n",
    "\n",
    "The tutorial was developed with a CW-Pro with the CW305 100t target FPGA; the observations made in the attack's development should be accurate if you're using the same, but other combinations of CW-Pro / CW-Lite / CW-Husky / CW305 100t / 35t / CW312T-A35 may behave somewhat differently (some definitely do!)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Setup\n",
    "\n",
    "See CW305_ECC_part1.ipynb for explanations which are not repeated here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#PLATFORM = 'CWLITE'\n",
    "#PLATFORM = 'CWPRO'\n",
    "PLATFORM = 'CWHUSKY'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "TARGET_PLATFORM = 'CW305_100t'\n",
    "#TARGET_PLATFORM = 'CW305_35t'\n",
    "#TARGET_PLATFORM = 'CW312T_A35'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "TRACES = 'HARDWARE' # if you have the required capture+target hardware: capture actual traces\n",
    "#TRACES = 'SIMULATED' # if you don't have capture+target hardware: use pre-captured traces (these traces were obtained using CW-Husky with a  CW305_100t)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import chipwhisperer as cw\n",
    "\n",
    "if TRACES != 'SIMULATED':\n",
    "    scope = cw.scope()\n",
    "    scope.default_setup()\n",
    "    if TARGET_PLATFORM == 'CW312T_A35':\n",
    "        scope.io.hs2 = 'clkgen'\n",
    "        fpga_id = 'cw312t_a35'\n",
    "        platform = 'ss2'\n",
    "    else:\n",
    "        scope.io.hs2 = \"disabled\"\n",
    "        platform = 'cw305'\n",
    "        if TARGET_PLATFORM == 'CW305_100t':\n",
    "            fpga_id = '100t'\n",
    "        elif TARGET_PLATFORM == 'CW305_35t':\n",
    "            fpga_id = '35t'\n",
    "\n",
    "    target = cw.target(scope, cw.targets.CW305_ECC, force=True, fpga_id=fpga_id, platform=platform)\n",
    "    \n",
    "    # ensure ADC is locked:\n",
    "    scope.clock.reset_adc()\n",
    "    assert (scope.clock.adc_locked), \"ADC failed to lock\"\n",
    "\n",
    "%run \"CW305_ECC_setup.ipynb\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#If you're using pre-recorded traces, skip ahead to the \"Trace Capture\" section."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# ensure ADC is locked:\n",
    "scope.clock.reset_adc()\n",
    "assert (scope.clock.adc_locked), \"ADC failed to lock\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Occasionally the ADC will fail to lock on the first try; when that happens, the above assertion will fail (and on the CW-Lite, the red LED will be on). Simply re-running the above cell again should fix things."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Trace Capture\n",
    "We start just like we did in the first part of this tutorial, by using a scalar for which we can very easily distinguish ones from zeros. Remember that k is the secret that we want to be able to retrieve with our side-channel attack."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "k = 0xffffffffffffffffffffffffffffffff00000000000000000000000000000000\n",
    "traces = get_traces(20, k, 'part2_1', full=False, samples_per_segment=64)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the first part, we omitted cycle 4203; including it improves the results: *(caveat: re-read the notes regarding setting `poi` in part 1!)*"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if TARGET_PLATFORM == 'CW312T_A35':\n",
    "    poi = [4202, -4203, 7, -8]\n",
    "else:\n",
    "    poi = [4202, -4203, -6, 7]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def update_plot(no_traces):\n",
    "    SS.data_source.data['y'] = get_sums(traces[:no_traces], poi)\n",
    "    push_notebook()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from bokeh.plotting import figure, show\n",
    "from bokeh.resources import INLINE\n",
    "from bokeh.io import push_notebook, output_notebook\n",
    "from ipywidgets import interact, Layout\n",
    "\n",
    "output_notebook(INLINE)\n",
    "\n",
    "S = figure(width=1200, x_axis_label='k bit index', y_axis_label='D')\n",
    "\n",
    "xrange = list(range(len(cycles)))\n",
    "sums = get_sums(traces[:10], poi)\n",
    "SS = S.line(xrange, sums, line_color='black')\n",
    "S.xaxis.axis_label_text_font_size = '20pt'\n",
    "S.yaxis.axis_label_text_font_size = '20pt'\n",
    "S.xaxis.major_label_text_font_size = '14pt'\n",
    "S.yaxis.major_label_text_font_size = '14pt'\n",
    "S.title.text_font_size = '20pt'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "show(S, notebook_handle=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "interact(update_plot, no_traces=(1, len(traces)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the first part of the tutorial, we learned that the leakage from cycles 6 and 7 during the processing of bit $i$ is actually linked to the value of bit $i-1$.\n",
    "\n",
    "The correlation attack accounted for this, but the difference of means attack did not! Let's correct that and see the effect on the results:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def update_corrected_plot(no_traces):\n",
    "    SSC.data_source.data['y'] = get_corrected_sums(traces[:no_traces], poi)\n",
    "    push_notebook()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "SC = figure(width=1200, x_axis_label='k bit index', y_axis_label='D')\n",
    "\n",
    "xrange = list(range(len(cycles)-1))\n",
    "sums = get_corrected_sums(traces[:10], poi)\n",
    "SSC = SC.line(xrange, sums, line_color='black')\n",
    "SC.xaxis.axis_label_text_font_size = '20pt'\n",
    "SC.yaxis.axis_label_text_font_size = '20pt'\n",
    "SC.xaxis.major_label_text_font_size = '14pt'\n",
    "SC.yaxis.major_label_text_font_size = '14pt'\n",
    "SC.title.text_font_size = '20pt'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "show(SC, notebook_handle=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "interact(update_corrected_plot, no_traces=(1, len(traces)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "On the above plots this doesn't seem to make much difference. But the fix only matters when successive $k$ bits differ, which for these traces occurs only once.\n",
    "\n",
    "Let's now measure traces with a patterned $k$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "k = 0x0000ffffffffff000000000000ffff00aaaa0000cccc00001111000033330000\n",
    "traces = get_traces(30, k, 'part2_2', full=False, samples_per_segment=530)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you re-run the previous cells to get the interactive plot for this new set of traces, you'll see that alternating 1/0 bits are properly distinguished (recall that they were not in part 1).\n",
    "\n",
    "We also see that the initial zeros have a different signature, and that the leading one also has a distinct signature.\n",
    "\n",
    "The SNR for these appears at least as good as that of the rest of the bits, so this should not pose a problem.\n",
    "\n",
    "Next we extract decision threshold from the collected traces, based on our known fixed $k$. In part 1, this was done manually, by visual inspection. By doing it programmatically here, we should be immune to differences in target and/or capture equipment.\n",
    "\n",
    "We'll later use these threshold to guess arbitrary $k$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sums = get_corrected_sums(traces, poi)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "poi_init_threshold = sums[16] - (sums[16] - np.average(sums[:16]))/2\n",
    "poi_reg_threshold = (np.average(sums[103:119]) - np.average(sums[56:103]))/2 + np.average(sums[56:103])\n",
    "thresholds = [poi_init_threshold, poi_reg_threshold]\n",
    "\n",
    "print('Init threhold: %3.2f, regular threshold: %3.2f' % (poi_init_threshold, poi_reg_threshold))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from bokeh.models import Span\n",
    "\n",
    "S = figure(width=1200, x_axis_label='k bit index', y_axis_label='D')\n",
    "\n",
    "xrange = list(range(len(cycles)-1))\n",
    "S.line(xrange, sums, line_color='black')\n",
    "\n",
    "ithreshold = Span(location=poi_init_threshold, dimension='width', line_color='green', line_width=2)\n",
    "rthreshold = Span(location=poi_reg_threshold, dimension='width', line_color='blue', line_width=2)\n",
    "S.renderers.extend([ithreshold, rthreshold])\n",
    "\n",
    "S.xaxis.axis_label_text_font_size = '20pt'\n",
    "S.yaxis.axis_label_text_font_size = '20pt'\n",
    "S.xaxis.major_label_text_font_size = '14pt'\n",
    "S.yaxis.major_label_text_font_size = '14pt'\n",
    "S.title.text_font_size = '20pt'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "show(S)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's do the same automatic decision threshold extraction for the correlation attack:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "rupdate_offset = 4195\n",
    "rupdate_cycles = 8\n",
    "rxread_offset = 205\n",
    "ryread_offset = 473\n",
    "rzread_offset = 17\n",
    "\n",
    "if TARGET_PLATFORM == 'CW312T_A35':\n",
    "    rupdate_cycles += 8\n",
    "    rupdate_offset -= 4\n",
    "    rxread_offset -= 4\n",
    "    ryread_offset -= 4\n",
    "    rzread_offset -= 4"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "corrs = get_corrs(traces)\n",
    "corr_init_threshold = (np.average(corrs[1:16]) - corrs[16])/2 + corrs[16]\n",
    "corr_reg_threshold = (np.average(corrs[16:56]) - np.average(corrs[56:104]))/2 + np.average(corrs[56:104])\n",
    "\n",
    "print('Init threhold: %3.2f, regular threshold: %3.2f' % (corr_init_threshold, corr_reg_threshold))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from bokeh.models import Span\n",
    "\n",
    "CC = figure(width=1200, x_axis_label='k bit index', y_axis_label='D')\n",
    "\n",
    "xrange = list(range(len(cycles)-1))\n",
    "CC.line(xrange, corrs, line_color='red')\n",
    "\n",
    "ithreshold = Span(location=corr_init_threshold, dimension='width', line_color='green', line_width=2)\n",
    "rthreshold = Span(location=corr_reg_threshold, dimension='width', line_color='blue', line_width=2)\n",
    "CC.renderers.extend([ithreshold, rthreshold])\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "show(CC)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Just going by the visual appearance of these results, we can be pretty sure that the DoM metric will outperform the correlation metric: here some $k$ bits are *very* close to the decision threhold. This is not the case for the DoM metric.\n",
    "\n",
    "We now have all that's required to check guesses, so let's do it:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sums = get_corrected_sums(traces, poi)\n",
    "guess = poi_guess(sums, thresholds)\n",
    "print(\"DoM: %s\" % check_guess(guess, k)[0])\n",
    "\n",
    "corrs = get_corrs(traces)\n",
    "guess = corr_guess(corrs)\n",
    "print(\"Correlation: %s\" % check_guess(guess, k)[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The DoM metric should be successful; the correlation metric is *usually* succesful but it's possible to have a handful of wrong bits.\n",
    "\n",
    "Next we'll pick a random $k$, collect many traces, and see how many traces are required to fully recover $k$, and at the same time see how many bits of $k$ are correctly guessed as we reduce the number of traces used in the attack.\n",
    "\n",
    "We'll apply the correlation attack at the same time to compare the two approaches."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "k = random_k()\n",
    "traces = get_traces(20, k, 'part2_3', full=False, samples_per_segment=530)\n",
    "if TRACES == 'SIMULATED':\n",
    "    k = traces[0][1]['k']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"Number of bits guessed wrong:\")\n",
    "print(\"# traces DoM Correlation\")\n",
    "for no_traces in range(len(traces), 0, -1):\n",
    "    sums = get_corrected_sums(traces[:no_traces], poi)\n",
    "    guess = poi_guess(sums, thresholds)\n",
    "    print(\"%3d    %3d \" % (no_traces, check_guess(guess, k)[1]), end='')\n",
    "\n",
    "    corrs = get_corrs(traces[:no_traces])\n",
    "    guess = corr_guess(corrs)\n",
    "    print(\"%3d\" % check_guess(guess, k)[1])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Clearly the DoM metric outperforms the correlation metric. From this point we will use only the DoM metric.\n",
    "\n",
    "The next step is to see how many bits are guessed correctly from a *single* trace, on average. We are no longer averaging traces: we collect a single trace for a random $k$ and run the attack on that single trace, and we repeat this many times to get the average number of correctly guessed bits.\n",
    "\n",
    "We do this to get closer to a real-world attack: recall that in normal usage, $k$ is only used for a single point multiply operation, which means that an attacker does not get to take the average of several traces."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "traces = get_traces(50, k, 'part2_4', full=False, samples_per_segment=64)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "wrong_bits = []\n",
    "for trace in traces:\n",
    "    sums = get_corrected_sums([trace], poi)\n",
    "    guess = poi_guess(sums, thresholds)\n",
    "    wrong_bits.append(check_guess(guess, trace.textin['k'])[1])\n",
    "\n",
    "print('Average wrong bits per trace: %f' % np.average(wrong_bits))\n",
    "print('Minimum wrong bits per trace: %f' % min(wrong_bits))\n",
    "print('Maximum wrong bits per trace: %f' % max(wrong_bits))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "These are great results: from a *single trace*, we can correctly guess most bits of $k$!\n",
    "\n",
    "Unfortunately, we don't know (yet) *which* bits we are correctly guessing, so we're not done yet.\n",
    "\n",
    "But we might be able to do more: it's reasonable to assume that correctly guessed bits tend to be further away from the decision thresholds. This may allow us to determine which bits can be correctly guessed. Let's see if this is the case.\n",
    "\n",
    "To do this, we need to define a new threshold to express how far the DoM measurement needs to be from the decision threshold in order for a bit guess to be accepted.\n",
    "\n",
    "In the `poi_guess_threshold()` function, we look at the distance that each DoM metric is from the decision threshold; we then take the difference between the maximum distance and the average distance, `base`. We then accept guesses as \"good\" if they are at least `threshold * base` away from the decision threshold. `threshold` must be greater than 0; the larger it is, the fewer bit guesses are accepted.\n",
    "\n",
    "There is no exact science behind this -- just heuristics!\n",
    "\n",
    "Let's see how many bit guesses get accepted as we vary the threshold, using the last collected power trace:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for threshold in [0.1, 0.3, 0.5, 0.7, 0.9]:\n",
    "    guess, guessed_bits = poi_guess_threshold(sums, threshold, thresholds)\n",
    "    print(\"Treshold = %0.1f: accepting %d guesses\" % (threshold, len(guessed_bits)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To visually validate, let's plot the location of accepted guesses on the DoM plot, when threshold is set to 0.7:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "guess, guessed_bits = poi_guess_threshold(sums, 0.7, thresholds)\n",
    "print('Guessing these bits: %s' % guessed_bits)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "T = figure(width=1800)\n",
    "xrange = list(range(len(sums)))\n",
    "T.line(xrange, sums, line_color=\"red\", line_width=2)\n",
    "rthreshold = Span(location=poi_reg_threshold, dimension='width', line_color='blue', line_width=2)\n",
    "ithreshold = Span(location=poi_init_threshold, dimension='width', line_color='green', line_width=2)\n",
    "T.renderers.extend([rthreshold, ithreshold])\n",
    "for b in guessed_bits:\n",
    "    T.renderers.extend([Span(location=b, dimension='height', line_color='black', line_width=1)])\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "show(T)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The vertical lines are where we would accept bit guesses.\n",
    "\n",
    "This should show that we are indeed picking the bits where the DoM is furthest away from the decision threshold.\n",
    "\n",
    "The next question is, can we set `threshold` such that all accepted guesses are actually good guesses?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "threshold = 0.7\n",
    "\n",
    "wrong_bits = []\n",
    "solid_guessed_bits = []\n",
    "total_wrong_bits = 0\n",
    "total_solid_guessed_bits = 0\n",
    "total_right_solid_guesses = 0\n",
    "total_wrong_solid_guesses = 0\n",
    "correct_solid_guesses = []\n",
    "all_wrong_bits = []\n",
    "    \n",
    "for trace in traces:\n",
    "    sums = get_corrected_sums([trace], poi)\n",
    "        \n",
    "    guess, tguessed_bits = poi_guess_threshold(sums, threshold, thresholds)\n",
    "    (status, num_wrong_bits, twrong_bits) = check_guess(guess, trace.textin['k'])\n",
    "\n",
    "    total_wrong_bits += num_wrong_bits\n",
    "    all_wrong_bits.append(num_wrong_bits)\n",
    "    total_solid_guessed_bits += len(tguessed_bits)\n",
    "    \n",
    "    wrong_solid_guesses = len(set(twrong_bits) & set(tguessed_bits))\n",
    "    right_solid_guesses = len(tguessed_bits) - wrong_solid_guesses\n",
    "    \n",
    "    total_wrong_solid_guesses += wrong_solid_guesses\n",
    "    total_right_solid_guesses += right_solid_guesses\n",
    "        \n",
    "    wrong_bits.append(twrong_bits)\n",
    "    solid_guessed_bits.append(tguessed_bits)\n",
    "    \n",
    "    correct_solid_guesses.append(list(set(tguessed_bits) - set(twrong_bits)))\n",
    "    \n",
    "print('All results are per-trace averages:')\n",
    "print('Average number of wrong bits (all guesses):     %5.1f' % (total_wrong_bits/len(traces)))\n",
    "print('Average number of solid guessed bits:           %5.1f' % (total_solid_guessed_bits/len(traces)))\n",
    "print('Average number of correct solid guessed bits:   %5.1f' % (total_right_solid_guesses/len(traces)))\n",
    "print('Average number of incorrect solid guessed bits: %5.1f' % (total_wrong_solid_guesses/len(traces)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the output above, \"solid guessed bits\" are guesses which we have accepted because they are above our filtering threshold.\n",
    "\n",
    "The last line, \"incorrect solid guessed bits\" are the number of accepted bit guesses that we hope are good but that are actually incorrect.\n",
    "\n",
    "Using `threshold=0.7`, you should find averages of around 14 correct guesses and 2 incorrect guesses (these numbers can vary a bit; you may need to tweak `threshold`).\n",
    "\n",
    "Increasing `threshold` to around 0.85 should bring the average number of incorrect guesses below 1."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# The Hidden Number Problem\n",
    "\n",
    "Thankfully, we are not done: it turns out that if you correctly guess enough bits and repeat this many times (i.e. make a lot of single-trace guesses, each for a different $k$), you can recover a full $k$ even if you could only guess a handful of bits for any given trace. This is thanks to the **Hidden Number Problem** (HNP). We won't go into the details of it because it's not simple, but HNP is a well-known and commonly used approach for side-channel analysis of public-key cryptography.\n",
    "\n",
    "A recent paper which used this technique is [A Side Journey to Titan](https://ninjalab.io/a-side-journey-to-titan/) from Ninjalabs. We follow their approach, which consists of collecting a large number of traces and keeping only the traces which meet at least one of the following conditions:\n",
    "1. at least three runs of 3 correct consecutive bit guesses\n",
    "2. at least two runs of 4 correct consecutive bit guesses\n",
    "3. at least one runs of 5 correct consecutive bit guesses\n",
    "\n",
    "We leave the actual solving of the HNP as a (non-trivial!) exercise to the reader; we finish here by seeing what is the percentage of traces that meet the above conditions.\n",
    "\n",
    "To get a good estimate, we need to collect a large number of traces. Since the traces are long, and yet we only care about $4 \\times 256 = 1024$ measurements from each 1.2 million point trace, it makes sense to modify our trace capture function to only save the points of interest from each trace.\n",
    "\n",
    "On Husky, we can get faster captures by using the new timed segmented feature. This also allows us to turn off streaming mode, which means that the target clock could be increased for even faster captures (we don't do this here, and if you do, you'll have to re-establish the decision thresholds). The timed segmented capture works like this:\n",
    "\n",
    "First, `scope.adc.segments` sets the number of segments to capture: 256 (one segment for each bit of $k$).\n",
    "\n",
    "Then, `scope.adc.segment_cycles` sets the interval at which we want to capture segments: 4204, the processing time for one bit, so that each segments starts at the same time index within the bit processing time.\n",
    "\n",
    "Finally, `scope.adc.samples` sets the number of samples to collect *per segment*: 10 (for a total of 10 * `scope.adc.segments` = 2560 samples).\n",
    "```\n",
    "        scope.adc.segments = 256\n",
    "        scope.adc.segment_cycles = 4204\n",
    "        scope.adc.samples = 10\n",
    "```\n",
    "\n",
    "We also set `scope.adc.offset` so that we capture the samples we are interested (e.g. our `poi` samples: `[6, 7, 4202, 4203]`); in this case we set it to 42 (start of first bit) + 4202 (offset into POI sample number 4202 within the bit) + 3 (Husky ADC offset). Then, once the segmented trace has been captured, we reconstruct a trace which retains only the four POI samples for each bit.\n",
    "\n",
    "Even with the care taken to record only the required power samples, pre-recorded traces for this step would be too large; so you'll need the required hardware to run this."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if TARGET_PLATFORM == 'CW312T_A35':\n",
    "    trace_segments = get_trace_segments(N=5000, poi=poi, randomize_k=True, husky_timed_segments=False, step='part2_5')\n",
    "else:\n",
    "    poi = [-6, 7, 4202, -4203] # pois need to be in this particular order for the Husky timed segmented capture to work\n",
    "    trace_segments = get_trace_segments(N=5000, poi=poi, randomize_k=True, husky_timed_segments=True, step='part2_5')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "consecutives(trace_segments=trace_segments, poi=poi, distance_threshold=0.67, thresholds=thresholds)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "consecutives(trace_segments=trace_segments, poi=poi, distance_threshold=0.75, thresholds=thresholds)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You should obtain approximately 4 good traces and 1 bad trace; some tweaking of `threshold` may be required. The point of this is to get an idea of how it's possible to collect mostly good bit guesses that are useful towards solving for the full $k$.\n",
    "\n",
    "The 5000 traces collected here are likely not sufficient for solving the EHNP; this is only intended as a demonstration to show that it is possible to make sufficient consecutive accurate guesses."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Conclusion\n",
    "\n",
    "In this part of the demo we've provided a roadmap towards an attack against real-world ECDSA, where each $k$ is only ever used once. Solving the EHNP is left as an exercise to the reader.\n",
    "\n",
    "In the next part we'll shift gears and look at design improvements to reduce the side-channel leakage. We'll use the attack developed here to evaluate the efficacy of our new countermeasures."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
